package com.example.springboot3demo.service.lock;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Objects;

// https://www.rqsay.com/article/p-9929255007519881.html
@Aspect
@Slf4j
@Component
public class DistributedLockAspect {

    @Resource
    private IDistributedLock distributedLock;
    /**
     * SpEL表达式解析
     */
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    /**
     * 用于获取方法参数名字
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@annotation(com.example.springboot3demo.service.lock.RedisLock)")
    public void distributorLock() {
    }

    @Around("distributorLock()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        RedisLock redisLock = this.getDistributedLock(pjp);
        // 获取lockKey
        String lockKey = this.getLockKey(pjp, redisLock);
        RLock lock = null;
        try {
            if (redisLock.tryLok()) {
                if (redisLock.tryTime() <= 0) {
                    throw new RuntimeException("tryTime must be greater than 0");
                }
                lock = distributedLock.tryLock(lockKey, redisLock.tryTime(), redisLock.lockTime(), redisLock.unit(), redisLock.fair());
            } else {
                lock = distributedLock.lock(lockKey, redisLock.lockTime(), redisLock.unit(), redisLock.fair());
            }
            if (Objects.isNull(lock)) {
                throw new RuntimeException("Duplicate request for method still in process");
            }
            return pjp.proceed();
        } catch (Exception e) {
            throw e;
        } finally {
            if (Objects.nonNull(lock)) {
                try {
                    this.distributedLock.unLock(lock);
                } catch (Exception e) {
                    log.error("分布式锁解锁异常", e);
                }
            }
        }
    }

    /**
     * @param pjp
     * @return
     * @throws NoSuchMethodException
     */
    private RedisLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {
        String methodName = pjp.getSignature().getName();
        Class clazz = pjp.getTarget().getClass();
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method lockMethod = clazz.getMethod(methodName, par);
        RedisLock redisLock = lockMethod.getAnnotation(RedisLock.class);
        return redisLock;
    }

    /***
     * 获取lockKey
     * @param pjp
     * @param redisLock
     * @return
     */
    private String getLockKey(ProceedingJoinPoint pjp, RedisLock redisLock) {
        String lockKey = redisLock.key();
        String keyPrefix = redisLock.keyPrefix();
        if (StringUtils.isBlank(lockKey)) {
            throw new RuntimeException("Lok key cannot be empty");
        }
        if (lockKey.contains("#")) {
            this.checkSpEL(lockKey);
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Object[] args = pjp.getArgs();
            lockKey = getValBySpEL(lockKey, methodSignature, args);
        }
        lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;
        return lockKey;
    }

    /**
     * 解析spEL表达式
     *
     * @param spEL
     * @param methodSignature
     * @param args
     * @return
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        // 获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        if (paramNames == null || paramNames.length < 1) {
            throw new RuntimeException("Lok key cannot be empty");
        }
        Expression expression = spelExpressionParser.parseExpression(spEL);
        // spring的表达式上下文对象
        EvaluationContext context = new StandardEvaluationContext();
        // 给上下文赋值
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }

    /**
     * SpEL 表达式校验
     *
     * @param spEL
     * @return
     */
    private void checkSpEL(String spEL) {
        try {
            ExpressionParser parser = new SpelExpressionParser();
            parser.parseExpression(spEL, new TemplateParserContext());
        } catch (Exception e) {
            log.error("spEL表达式解析异常", e);
            throw new RuntimeException("Invalid SpEL expression [" + spEL + "]");
        }
    }
}
